#include "LeGraphicsRuler.h"

#include <QtMath>
#include <QDebug>

LeGraphicsRuler::LeGraphicsRuler(QObject *parent) : QObject(parent),
    m_minimumValueStep(1.0),
    m_minimumTickSpacing(5),
    m_coarseMultiplier(10),
    m_minimumValue(0.0),
    m_maximumValue(100.0),
    m_size(0),
    m_needsUpdate(true),
    m_levelOfDetail(1.0)
{

}

qreal LeGraphicsRuler::minimumValueStep() const
{
    return m_minimumValueStep;
}

void LeGraphicsRuler::setMinimumValueStep(qreal minimumValueStep)
{
    if (qFuzzyCompare(m_minimumValueStep, minimumValueStep))
    {
        return;
    }
    m_minimumValueStep = minimumValueStep;
    invalidate();
}

qreal LeGraphicsRuler::minimumValue() const
{
    return m_minimumValue;
}

void LeGraphicsRuler::setMinimumValue(qreal minimumValue)
{
    if (qFuzzyCompare(m_minimumValue, minimumValue))
    {
        return;
    }
    m_minimumValue = minimumValue;
    invalidate();
}

qreal LeGraphicsRuler::maximumValue() const
{
    return m_maximumValue;
}

void LeGraphicsRuler::setMaximumValue(qreal maximumValue)
{
    if (qFuzzyCompare(m_maximumValue, maximumValue))
    {
        return;
    }
    m_maximumValue = maximumValue;
    invalidate();
}

qreal LeGraphicsRuler::ceilValue(qreal value) const
{
    maybeUpdate();
    auto reminder = std::fmod(value, m_minorValueStep);
    return value - reminder + m_minorValueStep;
}

qreal LeGraphicsRuler::floorValue(qreal value) const
{
    maybeUpdate();
    auto reminder = std::fmod(value, m_minorValueStep);
    return value - reminder;
}

qreal LeGraphicsRuler::roundValue(qreal value) const
{
    maybeUpdate();
    auto reminder = std::fmod(value, m_minorValueStep);
    if (reminder < (0.5 * m_minorValueStep))
    {
        return value - reminder;
    }
    else
    {
        return value - reminder + m_minorValueStep;
    }
}

QVector<qreal> LeGraphicsRuler::majorValues() const
{
    maybeUpdate();
    return m_majorValues;
}

qreal LeGraphicsRuler::majorValueStep() const
{
    maybeUpdate();
    return m_majorValueStep;
}

QVector<qreal> LeGraphicsRuler::minorValues() const
{
    maybeUpdate();
    return m_minorValues;
}

qreal LeGraphicsRuler::minorValueStep() const
{
    maybeUpdate();
    return m_minorValueStep;
}

int LeGraphicsRuler::minimumTickSpacing() const
{
    return m_minimumTickSpacing;
}

void LeGraphicsRuler::setMinimumTickSpacing(int minimumTickSpacing)
{
    if (m_minimumTickSpacing == minimumTickSpacing)
    {
        return;
    }
    m_minimumTickSpacing = minimumTickSpacing;
    invalidate();
}

int LeGraphicsRuler::coarseMultiplier() const
{
    return m_coarseMultiplier;
}

void LeGraphicsRuler::setCoarseMultiplier(int coarseMultiplier)
{
    if (m_coarseMultiplier == coarseMultiplier)
    {
        return;
    }
    m_coarseMultiplier = coarseMultiplier;
    invalidate();
}

int LeGraphicsRuler::size() const
{
    return m_size;
}

QVector<int> LeGraphicsRuler::majorTicks() const
{
    maybeUpdate();
    return m_majorTicks;
}

QVector<int> LeGraphicsRuler::minorTicks() const
{
    maybeUpdate();
    return m_minorTicks;
}

int LeGraphicsRuler::tickPosition(qreal value) const
{
    maybeUpdate();
    return calculateTickPosition(value);
}

void LeGraphicsRuler::setSize(int size)
{
    if (m_size == size)
    {
        return;
    }
    m_size = size;
    invalidate();
}

void LeGraphicsRuler::invalidate() const
{
    m_needsUpdate = true;
    emit changed();
}

void LeGraphicsRuler::maybeUpdate() const
{
    if (!m_needsUpdate)
    {
        return;
    }

    m_levelOfDetail = m_size / qAbs(m_maximumValue - m_minimumValue);
    qreal smallestStep = qMax(m_minimumValueStep, qreal(m_minimumTickSpacing) / m_levelOfDetail);

    m_minorValueStep = normaliseValueStep(smallestStep);
    m_majorValueStep = m_minorValueStep * m_coarseMultiplier;
    updateValueSeries(m_minorValues, m_minorValueStep);
    updateValueSeries(m_majorValues, m_majorValueStep);
    updateTickSeries(m_minorTicks, m_minorValues);
    updateTickSeries(m_majorTicks, m_majorValues);

    m_needsUpdate = false;
}

qreal LeGraphicsRuler::normaliseValueStep(qreal step)
{
    qreal exponent = qFloor(std::log10(step)/std::log10(10));
    qreal magnitude = std::pow(10.0f, exponent);
    qreal residual = step / magnitude;
    qreal factor = 1.0;
    if (residual > 5.0)
        factor = 10.0;
    else if (residual > 2.0)
        factor = 5.0;
    else if (residual > 1.0)
        factor = 2.0;
    return magnitude * factor;

}

int LeGraphicsRuler::calculateTickPosition(qreal value) const
{
    int pos;
    if (m_minimumValue < m_maximumValue)
        pos = qCeil(m_levelOfDetail * (value - m_minimumValue) - 1.0);
    else
        pos = m_size - qCeil(m_levelOfDetail * (value - m_maximumValue) - 1.0);
    return pos;
}

void LeGraphicsRuler::updateValueSeries(QVector<qreal> &valueSeries, qreal step) const
{
    if (step == 0)
    {
        return;
    }

    const qreal range = qAbs(m_maximumValue - m_minimumValue);
    const int steps = qCeil(range / step);

    valueSeries.resize(steps);
    if (steps == 0)
    {
        return;
    }

    if (m_minimumValue < m_maximumValue)
    {
        const qreal offset = std::fmod(m_minimumValue, step);
        for (int i = 0; i < steps; i++)
        {
            valueSeries[i] = m_minimumValue - offset + i * step;
        }
    }
    else
    {
        const qreal offset = std::fmod(m_maximumValue, step);
        for (int i = 0; i < steps; i++)
        {
            valueSeries[i] = m_maximumValue - offset + i * step;
        }
    }
}

void LeGraphicsRuler::updateTickSeries(QVector<int> &tickSeries, const QVector<qreal> &valueSeries) const
{
    tickSeries.resize(valueSeries.size());
    if (valueSeries.size() == 0)
    {
        return;
    }
    for (int i = 0; i < valueSeries.size(); i++)
    {
        tickSeries[i] = calculateTickPosition(valueSeries[i]);
    }
}
